<?php

namespace app\common\library\wechat;

use app\common\model\Wxapp as WxappModel;
use app\common\enum\OrderType as OrderTypeEnum;
use app\common\enum\order\PayType as PayTypeEnum;
use app\common\exception\BaseException;
use app\common\library\wechat\WxSubMsg;
/**
 * 微信支付
 * Class WxPay
 * @package app\common\library\wechat
 */
class WxPay extends WxBase
{
    // 微信支付配置
    private $config;

    // 订单模型
    private $modelClass = [
        OrderTypeEnum::MASTER => 'app\api\service\order\PaySuccess',
        OrderTypeEnum::SHARING => 'app\api\service\sharing\order\PaySuccess',
        OrderTypeEnum::RECHARGE => 'app\api\service\recharge\PaySuccess',
    ];

    /**
     * 构造函数
     * WxPay constructor.
     * @param $config
     */
    public function __construct($config = false)
    {
        parent::__construct();
        $this->config = $config;
        $this->config !== false && $this->setConfig($this->config['app_id'], $this->config['app_secret']);
    }

    /**
     * 统一下单API
     * @param $order_no
     * @param $openid
     * @param $totalFee
     * @param int $orderType 订单类型
     * @return array
     * @throws BaseException
     */
    public function unifiedorder($order_no, $openid, $totalFee, $orderType = OrderTypeEnum::MASTER)
    {
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time . $openid);
        // API参数
        $params = [
            'appid' => $this->appId,
            'attach' => json_encode(['order_type' => $orderType]),
            'body' => $order_no,
            'mch_id' => $this->config['mchid'],
            'nonce_str' => $nonceStr,
            'notify_url' => base_url() . 'notice.php',  // 异步通知地址
            'openid' => $openid,
            'out_trade_no' => $order_no,
            'spbill_create_ip' => \request()->ip(),
            'total_fee' => $totalFee * 100, // 价格:单位分
            'trade_type' => 'JSAPI',
        ];
        // dump($params);die;
        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 记录日志
        $this->doLogs(['name' => '微信支付统一下单API', 'params' => $params]);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $result = $this->post($url, $this->toXml($params));
        $prepay = $this->fromXml($result);
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            throw new BaseException(['msg' => "微信支付api：{$prepay['return_msg']}", 'code' => -10]);
        }
        if ($prepay['result_code'] === 'FAIL') {
            throw new BaseException(['msg' => "微信支付api：{$prepay['err_code_des']}", 'code' => -10]);
        }
        // 生成 nonce_str 供前端使用
        $paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time);
        return [
            'prepay_id' => $prepay['prepay_id'],
            'nonceStr' => $nonceStr,
            'timeStamp' => (string)$time,
            'paySign' => $paySign
        ];
    }
    public function notify1()
    {
       $xml = <<<EOF
<xml><appid><![CDATA[wx8908532a27c5dd4f]]></appid>
<attach><![CDATA[{"order_type":10}]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1509822581]]></mch_id>
<nonce_str><![CDATA[ca1fe6d2b4f667cf249bd1d7176c6178]]></nonce_str>
<openid><![CDATA[oZDDE5JLnVyc6qe6nbNWdbFHtY5I]]></openid>
<out_trade_no><![CDATA[2025092610110155]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[3880232710B7328822D079DC405FB09D]]></sign>
<time_end><![CDATA[20190401104804]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[4200000265201904014227830207]]></transaction_id>
</xml>
EOF;

        // if (!$xml = file_get_contents('php://input')) {
        //     $this->returnCode(false, 'Not found DATA');
        // }
        // 将服务器返回的XML数据转化为数组
        $data = $this->fromXml($xml);
        // 记录日志
        $this->doLogs($xml);
        $this->doLogs($data);
        // 实例化订单模型
        $model = $this->getOrderModel($data['out_trade_no'], $data['attach']);
        // 订单信息
        $order = $model->getOrderInfo();
        // empty($order) && $this->returnCode(false, '订单不存在');
        // 小程序配置信息
        $wxConfig = WxappModel::getWxappCache($order['wxapp_id']);
        // 设置支付秘钥
        $this->config['apikey'] = $wxConfig['apikey'];
        // 保存微信服务器返回的签名sign
        $dataSign = $data['sign'];
        // sign不参与签名算法
        unset($data['sign']);
        // 生成签名
        $sign = $this->makeSign($data);
        // 判断签名是否正确 判断支付状态
        // if (
        //     ($sign !== $dataSign)
        //     || ($data['return_code'] !== 'SUCCESS')
        //     || ($data['result_code'] !== 'SUCCESS')
        // ) {
        //     $this->returnCode(false, '签名失败');
        // }
        // 新增业务处理
        $arr = DB("order")->where("order_no",$data['out_trade_no'])->find();
        $brr = DB("order_goods")->where("order_id",$arr['order_id'])->find();
        // if ($brr) {
        //     $where['goods_id'] = $brr['goods_id'];
        //     $where['day'] = $brr['jiuzhen_day'];
        //     // dump($where);die;
        //     DB("goods_paiban")->where($where)->setDec($this->getTimePeriod($brr['jiuzhen_time']));
        // }
        if ($brr && $arr['order_type'] == 1) {
            $goodsId = $brr['goods_id'];
            $jiuzhenDay = $brr['jiuzhen_day'];
            $jiuzhenTime = $brr['jiuzhen_time'];
        
            // 计算 week_day（1~7，星期一~星期日）
            $weekDay = date('N', strtotime($jiuzhenDay));
        
            // 获取字段名称（morning_num / afternoon_num / night_num）
            $timeField = $this->getTimePeriodField($jiuzhenTime); // 自定义方法，返回 morning_num 等
            // dump($timeField);die;
            // 减项
            DB("goods_weekly_schedule")
                ->where([
                    'goods_id' => $goodsId,
                    'week_day' => $weekDay,
                ])
                ->setDec($timeField);
        }
        // 订单支付成功业务处理
        // dump($data);die;
        $status = $model->onPaySuccess(PayTypeEnum::WECHAT, $data);
        // 新增发送消息
        // 开始发送消息
        // 获取小程序配置
        $wxappId = "10001";
        $wxConfig = WxappModel::getWxappCache($wxappId);
        // 请求微信api执行发送
        $WxSubMsg = new WxSubMsg($wxConfig['app_id'], $wxConfig['app_secret']);
        $params = [
            'touser' => DB("user")->where("user_id", $order['user_id'])->find()['open_id'],
            'template_id' => 'mViO0z3XgbCs-t0OTHSRPAQ7eo4V18E_mgQlbB6d_sY',
            'page' => '',
            'data' => [
                'name5' => ['value' => DB("user_jiuzhen")->where("jiuzhen_id", $order['goods'][0]['jiuzhen_id'])->find()['name'] ?: '默认姓名'], // 确保字段有默认值
                'name9' => ['value' => $order['goods'][0]['goods_name'] ?: '默认商品名称'], // 默认商品名称
                'thing7' => ['value' => DB("category")->where('category_id', DB("goods")->where('goods_id', $order['goods'][0]['goods_id'])->find()['category_id'])->find()['name'] ?: '默认分类'], // 默认分类
                'date1' => ['value' => $order['goods'][0]['jiuzhen_day'] ?: '无预约日期'], // 默认日期
                'thing4' => ['value' => "您好，您已成功预约"], // 确保字段结构一致
            ],
        ];
        if ($arr['order_type'] == 1) {
            $WxSubMsg->sendTemplateMessage($params);
        }
        // 新增发送结束
        if ($status == false) {
            $this->returnCode(false, $model->getError());
        }
        // 处理
        // 返回状态
        $this->returnCode(true, 'OK');
    }
    /**
     * 支付成功异步通知
     * @throws BaseException
     * @throws \Exception
     * @throws \think\exception\DbException
     */
    public function notify()
    {
//        $xml = <<<EOF
// <xml><appid><![CDATA[wx8908532a27c5dd4f]]></appid>
// <attach><![CDATA[{"order_type":10}]]></attach>
// <bank_type><![CDATA[CFT]]></bank_type>
// <cash_fee><![CDATA[1]]></cash_fee>
// <fee_type><![CDATA[CNY]]></fee_type>
// <is_subscribe><![CDATA[N]]></is_subscribe>
// <mch_id><![CDATA[1509822581]]></mch_id>
// <nonce_str><![CDATA[ca1fe6d2b4f667cf249bd1d7176c6178]]></nonce_str>
// <openid><![CDATA[oZDDE5JLnVyc6qe6nbNWdbFHtY5I]]></openid>
// <out_trade_no><![CDATA[2025090599575149]]></out_trade_no>
// <result_code><![CDATA[SUCCESS]]></result_code>
// <return_code><![CDATA[SUCCESS]]></return_code>
// <sign><![CDATA[3880232710B7328822D079DC405FB09D]]></sign>
// <time_end><![CDATA[20190401104804]]></time_end>
// <total_fee>1</total_fee>
// <trade_type><![CDATA[JSAPI]]></trade_type>
// <transaction_id><![CDATA[4200000265201904014227830207]]></transaction_id>
// </xml>
// EOF;

        if (!$xml = file_get_contents('php://input')) {
            $this->returnCode(false, 'Not found DATA');
        }
        // 将服务器返回的XML数据转化为数组
        $data = $this->fromXml($xml);
        // 记录日志
        $this->doLogs($xml);
        $this->doLogs($data);
        // 实例化订单模型
        $model = $this->getOrderModel($data['out_trade_no'], $data['attach']);
        // 订单信息
        $order = $model->getOrderInfo();
        // empty($order) && $this->returnCode(false, '订单不存在');
        // 小程序配置信息
        $wxConfig = WxappModel::getWxappCache($order['wxapp_id']);
        // 设置支付秘钥
        $this->config['apikey'] = $wxConfig['apikey'];
        // 保存微信服务器返回的签名sign
        $dataSign = $data['sign'];
        // sign不参与签名算法
        unset($data['sign']);
        // 生成签名
        $sign = $this->makeSign($data);
        // 判断签名是否正确 判断支付状态
        if (
            ($sign !== $dataSign)
            || ($data['return_code'] !== 'SUCCESS')
            || ($data['result_code'] !== 'SUCCESS')
        ) {
            $this->returnCode(false, '签名失败');
        }
        // 新增业务处理
        $arr = DB("order")->where("order_no",$data['out_trade_no'])->find();
        $brr = DB("order_goods")->where("order_id",$arr['order_id'])->find();
        // if ($brr) {
        //     $where['goods_id'] = $brr['goods_id'];
        //     $where['day'] = $brr['jiuzhen_day'];
        //     // dump($where);die;
        //     DB("goods_paiban")->where($where)->setDec($this->getTimePeriod($brr['jiuzhen_time']));
        // }
        if ($brr && $arr['order_type'] == 1) {
            $goodsId = $brr['goods_id'];
            $jiuzhenDay = $brr['jiuzhen_day'];
            $jiuzhenTime = $brr['jiuzhen_time'];
        
            // 计算 week_day（1~7，星期一~星期日）
            $weekDay = date('N', strtotime($jiuzhenDay));
        
            // 获取字段名称（morning_num / afternoon_num / night_num）
            $timeField = $this->getTimePeriodField($jiuzhenTime); // 自定义方法，返回 morning_num 等
        
            // 减项
            DB("goods_weekly_schedule")
                ->where([
                    'goods_id' => $goodsId,
                    'week_day' => $weekDay,
                ])
                ->setDec($timeField);
        }
        // 订单支付成功业务处理
        $status = $model->onPaySuccess(PayTypeEnum::WECHAT, $data);
        // 新增发送消息
        // 开始发送消息
        // 获取小程序配置
        $wxappId = "10001";
        $wxConfig = WxappModel::getWxappCache($wxappId);
        // 请求微信api执行发送
        $WxSubMsg = new WxSubMsg($wxConfig['app_id'], $wxConfig['app_secret']);
        $params = [
            'touser' => DB("user")->where("user_id", $order['user_id'])->find()['open_id'],
            'template_id' => 'mViO0z3XgbCs-t0OTHSRPAQ7eo4V18E_mgQlbB6d_sY',
            'page' => '',
            'data' => [
                'name5' => ['value' => DB("user_jiuzhen")->where("jiuzhen_id", $order['goods'][0]['jiuzhen_id'])->find()['name'] ?: '默认姓名'], // 确保字段有默认值
                'name9' => ['value' => $order['goods'][0]['goods_name'] ?: '默认商品名称'], // 默认商品名称
                'thing7' => ['value' => DB("category")->where('category_id', DB("goods")->where('goods_id', $order['goods'][0]['goods_id'])->find()['category_id'])->find()['name'] ?: '默认分类'], // 默认分类
                'date1' => ['value' => $order['goods'][0]['jiuzhen_day'] ?: '无预约日期'], // 默认日期
                'thing4' => ['value' => "您好，您已成功预约"], // 确保字段结构一致
            ],
        ];
        if ($arr['order_type'] == 1) {
            $WxSubMsg->sendTemplateMessage($params);
        }
        // 新增发送结束
        if ($status == false) {
            $this->returnCode(false, $model->getError());
        }
        // 处理
        // 返回状态
        $this->returnCode(true, 'OK');
    }
    private function getTimePeriodField($timeRange)
    {
        [$start, $end] = explode('-', $timeRange);
        $hour = (int)explode(':', $start)[0];

        if ($hour < 12) {
            return 'morning_num';
        } elseif ($hour < 17) {
            return 'afternoon_num';
        } else {
            return 'night_num';
        }
    }
    public function getTimePeriod($timeRange) {
        // 分割时间范围并取开始时间
        [$start, $end] = explode('-', $timeRange);
        // 提取小时部分
        $hour = (int)explode(':', $start)[0];
        
        // 判断时间段
        return $hour < 12 ? 'morning_num' : 'afternoon_num';
    }
    /**
     * 申请退款API
     * @param $transaction_id
     * @param double $total_fee 订单总金额
     * @param double $refund_fee 退款金额
     * @return bool
     * @throws BaseException
     */
    public function refund($transaction_id, $total_fee, $refund_fee)
    {
        // 当前时间
        $time = time();
        // 生成随机字符串
        $nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee);
        // API参数
        $params = [
            'appid' => $this->appId,
            'mch_id' => $this->config['mchid'],
            'nonce_str' => $nonceStr,
            'transaction_id' => $transaction_id,
            'out_refund_no' => $time,
            'total_fee' => $total_fee * 100,
            'refund_fee' => $refund_fee * 100,
        ];
        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $result = $this->post($url, $this->toXml($params), true, $this->getCertPem());
        // 请求失败
        if (empty($result)) {
            throw new BaseException(['msg' => '微信退款api请求失败']);
        }
        // 格式化返回结果
        $prepay = $this->fromXml($result);
        // 记录日志
        log_write(['describe' => '微信退款API', [
            'params' => $params,
            'result' => $result,
            'prepay' => $prepay
        ]]);
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            throw new BaseException(['msg' => 'return_msg: ' . $prepay['return_msg']]);
        }
        if ($prepay['result_code'] === 'FAIL') {
            throw new BaseException(['msg' => 'err_code_des: ' . $prepay['err_code_des']]);
        }
        return true;
    }

    /**
     * 企业付款到零钱API
     * @param $order_no
     * @param $openid
     * @param $amount
     * @param $desc
     * @return bool
     * @throws BaseException
     */
    public function transfers($order_no, $openid, $amount, $desc)
    {
        // API参数
        $params = [
            'mch_appid' => $this->appId,
            'mchid' => $this->config['mchid'],
            'nonce_str' => md5(uniqid()),
            'partner_trade_no' => $order_no,
            'openid' => $openid,
            'check_name' => 'NO_CHECK',
            'amount' => $amount * 100,
            'desc' => $desc,
            'spbill_create_ip' => \request()->ip(),
        ];
        // 生成签名
        $params['sign'] = $this->makeSign($params);
        // 请求API
        $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
        $result = $this->post($url, $this->toXml($params), true, $this->getCertPem());
        // 请求失败
        if (empty($result)) {
            throw new BaseException(['msg' => '微信退款api请求失败']);
        }
        // 格式化返回结果
        $prepay = $this->fromXml($result);
        // 请求失败
        if ($prepay['return_code'] === 'FAIL') {
            throw new BaseException(['msg' => 'return_msg: ' . $prepay['return_msg']]);
        }
        if ($prepay['result_code'] === 'FAIL') {
            throw new BaseException(['msg' => 'err_code_des: ' . $prepay['err_code_des']]);
        }
        return true;
    }

    /**
     * 获取cert证书文件
     * @return array
     * @throws BaseException
     */
    private function getCertPem()
    {
        if (empty($this->config['cert_pem']) || empty($this->config['key_pem'])) {
            throw new BaseException(['msg' => '请先到后台小程序设置填写微信支付证书文件']);
        }
        // cert目录
        $filePath = __DIR__ . '/cert/' . $this->config['wxapp_id'] . '/';
        return [
            'certPem' => $filePath . 'cert.pem',
            'keyPem' => $filePath . 'key.pem'
        ];
    }

    /**
     * 实例化订单模型 (根据attach判断)
     * @param $orderNo
     * @param null $attach
     * @return mixed
     */
    private function getOrderModel($orderNo, $attach = null)
    {
        $attach = json_decode($attach, true);
        // 判断订单类型返回对应的订单模型
        $model = $this->modelClass[$attach['order_type']];
        return new $model($orderNo);
    }

    /**
     * 返回状态给微信服务器
     * @param boolean $returnCode
     * @param string $msg
     */
    private function returnCode($returnCode = true, $msg = null)
    {
        // 返回状态
        $return = [
            'return_code' => $returnCode ? 'SUCCESS' : 'FAIL',
            'return_msg' => $msg ?: 'OK',
        ];
        // 记录日志
        log_write([
            'describe' => '返回微信支付状态',
            'data' => $return
        ]);
        die($this->toXml($return));
    }

    /**
     * 生成paySign
     * @param $nonceStr
     * @param $prepay_id
     * @param $timeStamp
     * @return string
     */
    private function makePaySign($nonceStr, $prepay_id, $timeStamp)
    {
        $data = [
            'appId' => $this->appId,
            'nonceStr' => $nonceStr,
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => 'MD5',
            'timeStamp' => $timeStamp,
        ];
        // 签名步骤一：按字典序排序参数
        ksort($data);
        $string = $this->toUrlParams($data);
        // 签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->config['apikey'];
        // 签名步骤三：MD5加密
        $string = md5($string);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     */
    private function fromXml($xml)
    {
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * 生成签名
     * @param $values
     * @return string 本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    private function makeSign($values)
    {
        //签名步骤一：按字典序排序参数
        ksort($values);
        $string = $this->toUrlParams($values);
        //签名步骤二：在string后加入KEY
        $string = $string . '&key=' . $this->config['apikey'];
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     * @param $values
     * @return string
     */
    private function toUrlParams($values)
    {
        $buff = '';
        foreach ($values as $k => $v) {
            if ($k != 'sign' && $v != '' && !is_array($v)) {
                $buff .= $k . '=' . $v . '&';
            }
        }
        return trim($buff, '&');
    }

    /**
     * 输出xml字符
     * @param $values
     * @return bool|string
     */
    private function toXml($values)
    {
        if (!is_array($values)
            || count($values) <= 0
        ) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

}
